package com.easylinkin.linkappapi.location.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easylinkin.linkappapi.building.entity.Floor;
import com.easylinkin.linkappapi.building.service.BuildingService;
import com.easylinkin.linkappapi.building.service.FloorService;
import com.easylinkin.linkappapi.building.vo.BuildingFloorVO;
import com.easylinkin.linkappapi.common.exceptions.BusinessException;
import com.easylinkin.linkappapi.common.model.RequestModel;
import com.easylinkin.linkappapi.common.utils.excel.ExcelReadUtil;
import com.easylinkin.linkappapi.common.utils.location.ThreePointLocationUtil;
import com.easylinkin.linkappapi.device.entity.Device;
import com.easylinkin.linkappapi.device.service.DeviceService;
import com.easylinkin.linkappapi.lobar.dto.LaborRegionDTO;
import com.easylinkin.linkappapi.lobar.dto.UserCertificateDTO;
import com.easylinkin.linkappapi.lobar.dto.UserProjectDTO;
import com.easylinkin.linkappapi.lobar.dto.excel.ExcelResultDTO;
import com.easylinkin.linkappapi.lobar.dto.excel.ExcelResultDetailDTO;
import com.easylinkin.linkappapi.lobar.entity.LobarSet;
import com.easylinkin.linkappapi.lobar.entity.UserCertificate;
import com.easylinkin.linkappapi.lobar.entity.UserProject;
import com.easylinkin.linkappapi.lobar.mapper.UserProjectMapper;
import com.easylinkin.linkappapi.lobar.service.LaborRegionService;
import com.easylinkin.linkappapi.lobar.service.LobarSetService;
import com.easylinkin.linkappapi.lobar.service.UserCertificateService;
import com.easylinkin.linkappapi.lobar.service.UserProjectService;
import com.easylinkin.linkappapi.lobar.util.GaodeUtils;
import com.easylinkin.linkappapi.lobar.util.IdentityUtils;
import com.easylinkin.linkappapi.location.entity.AppFloorBeacon;
import com.easylinkin.linkappapi.location.entity.LinkappLocationDevice;
import com.easylinkin.linkappapi.location.entity.LinkappLocationRecord;
import com.easylinkin.linkappapi.location.entity.LinkappLocationRecordDetail;
import com.easylinkin.linkappapi.location.entity.dto.DeviceQueryDto;
import com.easylinkin.linkappapi.location.entity.dto.DeviceTypeDto;
import com.easylinkin.linkappapi.location.entity.dto.IndoorLocationDto;
import com.easylinkin.linkappapi.location.entity.dto.PeopleQueryDto;
import com.easylinkin.linkappapi.location.entity.vo.AreaObject;
import com.easylinkin.linkappapi.location.entity.vo.LocationDeviceVo;
import com.easylinkin.linkappapi.location.entity.vo.PeopleInfoVo;
import com.easylinkin.linkappapi.location.entity.vo.ScreenCountVo;
import com.easylinkin.linkappapi.location.entity.vo.*;
import com.easylinkin.linkappapi.location.mapper.AppFloorBeaconMapper;
import com.easylinkin.linkappapi.location.mapper.LinkappLocationDeviceMapper;
import com.easylinkin.linkappapi.location.service.ILinkappLocationDeviceService;
import com.easylinkin.linkappapi.location.service.ILinkappLocationRecordDetailService;
import com.easylinkin.linkappapi.location.service.ILinkappLocationRecordService;
import com.easylinkin.linkappapi.location.vo.AppFloorBeaconVO;
import com.easylinkin.linkappapi.openapi.dto.DatapushDTO;
import com.easylinkin.linkappapi.security.context.LinkappUserContextProducer;
import com.easylinkin.linkappapi.security.entity.LinkappUser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import site.morn.rest.RestBuilders;
import site.morn.rest.RestMessage;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 定位设备表 服务实现类
 *
 * @author JG
 * @since 2023-09-20
 */
@Service
public class LinkappLocationDeviceServiceImpl extends ServiceImpl<LinkappLocationDeviceMapper, LinkappLocationDevice> implements ILinkappLocationDeviceService {

    private static final Logger logger = LoggerFactory.getLogger(LinkappLocationDeviceServiceImpl.class);

    @Resource
    private LinkappUserContextProducer linkappUserContextProducer;

    @Autowired
    private LinkappLocationDeviceMapper linkappLocationDeviceMapper;

    @Autowired
    private UserProjectService userProjectService;

    @Autowired
    private UserProjectMapper userProjectMapper;

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private AppFloorBeaconMapper floorBeaconMapper;

    @Autowired
    private ILinkappLocationRecordService linkappLocationRecordService;

    @Autowired
    private ILinkappLocationRecordDetailService linkappLocationRecordDetailService;

    @Autowired
    private UserCertificateService userCertificateService;

    @Autowired
    private FloorService floorService;

    @Autowired
    private LobarSetService lobarSetService;

    @Autowired
    private LaborRegionService laborRegionService;

    @Autowired
    private BuildingService buildingService;

    /**
    * @Description: 安全帽分页列表查询
    * @Param: [requestModel]
    * @return: site.morn.rest.RestMessage
    * @Author: 胡明威
    * @Date: 2023/9/20
    */
    @Override
    public RestMessage queryList(RequestModel<DeviceQueryDto> requestModel) {
        // 获取当前登陆人信息
        LinkappUser currentUser = linkappUserContextProducer.getNotNullCurrent();
        IPage<LocationDeviceVo> page = new Page<>(requestModel.getPage().getCurrent(), requestModel.getPage().getSize());
        requestModel.getCustomQueryParams().setTenantId(currentUser.getTenantId());
        List<LocationDeviceVo> locationDeviceVos = linkappLocationDeviceMapper.queryList(requestModel.getCustomQueryParams(), page);
        page.setRecords(locationDeviceVos);
        return RestBuilders.successBuilder().data(page).build();
    }

    /**
    * @Description: 批量绑定定位设备
    * @Param: [file, tenantId]
    * @return: com.easylinkin.linkappapi.lobar.dto.excel.ExcelResultDTO
    * @Author: 胡明威
    * @Date: 2023/9/21
    */
    @Override
    public ExcelResultDTO importExcel(MultipartFile file, LinkappUser currentUserInfo) throws Exception {
        // 读取excel文件数据
        List<List<String>> excelList = ExcelReadUtil.getExcelInfo(file, 3, -1, -1);
        if (excelList.size() > 0 && excelList.get(0).size() < 3) {
            throw new BusinessException("模板错误,请选择正确的文件导入");
        }

        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        CompletableFuture<ExcelResultDTO> completableFuture = CompletableFuture
                .supplyAsync(() -> {
                    ExcelResultDTO excelResult = getExcelResult(excelList, currentUserInfo);
                    return excelResult;
                }, executorService);
        ExcelResultDTO excelResultDTO = completableFuture.get();
        return excelResultDTO;
    }

    /**
    * @Description: 执行验证和绑定
    * @Param: [excelList, tenantId]
    * @return: com.easylinkin.linkappapi.lobar.dto.excel.ExcelResultDTO
    * @Author: 胡明威
    * @Date: 2023/9/21
    */
    private ExcelResultDTO getExcelResult(List<List<String>> excelList, LinkappUser currentUserInfo) {
        // 定义必填字段的索引
        int deviceCodeIndex = 0;
        int nameIndex = 1;
        int cardNoIndex = 2;

        String tenantId = currentUserInfo.getTenantId();
        ExcelResultDTO excelResultDTO = new ExcelResultDTO();
        Set<String> cardSet = new HashSet<>();
        List<ExcelResultDetailDTO> excelResultDetailDTOS = new ArrayList<>();
        int successNum = 0;
        // 从第4行开始读取数据
        int row = 4;
        for (List<String> item : excelList) {
            ExcelResultDetailDTO excelResultDetailDTO = new ExcelResultDetailDTO();
            //异常提示信息
            List<String> msg = new ArrayList<>();
            // 1、校验设备编号是否存在
            boolean checkDeviceCodeFlag = checkDeviceCode(item.get(deviceCodeIndex).trim(), msg, tenantId);
            if (!checkDeviceCodeFlag) {
                excelResultDetailDTO.setDetail(StringUtils.join(msg, ","));
                excelResultDetailDTO.setType(1);
                excelResultDetailDTO.setTypeName("失败");
                excelResultDetailDTO.setNo(row++);
                excelResultDetailDTO.setName(item.get(deviceCodeIndex));
                excelResultDetailDTOS.add(excelResultDetailDTO);
                continue;
            }

            // 2、校验姓名是否存在于花名册内
            boolean checkNameFlag = checkName(item.get(nameIndex).trim(), msg, tenantId);
            if (!checkNameFlag) {
                excelResultDetailDTO.setDetail(StringUtils.join(msg, ","));
                excelResultDetailDTO.setType(1);
                excelResultDetailDTO.setTypeName("失败");
                excelResultDetailDTO.setNo(row++);
                excelResultDetailDTO.setName(item.get(deviceCodeIndex));
                excelResultDetailDTOS.add(excelResultDetailDTO);
                continue;
            }

            // 3、校验身份证号格式是否正确，并且是否存在于花名册中
            String userId = checkCardNo(item.get(cardNoIndex).trim(), msg, tenantId, item.get(nameIndex).trim(), cardSet);
            if (StringUtils.isBlank(userId)) {
                excelResultDetailDTO.setDetail(StringUtils.join(msg, ","));
                excelResultDetailDTO.setType(1);
                excelResultDetailDTO.setTypeName("失败");
                excelResultDetailDTO.setNo(row++);
                excelResultDetailDTO.setName(item.get(deviceCodeIndex));
                excelResultDetailDTOS.add(excelResultDetailDTO);
                continue;
            }

            // 绑定人员
            LinkappLocationDevice locationDevice = new LinkappLocationDevice();
            locationDevice.setUserId(userId);
            locationDevice.setBindState(1);
            locationDevice.setBindTime(new Date());
            locationDevice.setModifyId(currentUserInfo.getId());
            locationDevice.setModifyTime(new Date());
            UpdateWrapper<LinkappLocationDevice> updateWrapper = new UpdateWrapper<>();
            updateWrapper.eq("device_code_", item.get(deviceCodeIndex));
            updateWrapper.eq("tenant_id_", tenantId);
            updateWrapper.eq("delete_state_", true);
            update(locationDevice, updateWrapper);

            // 记录成功次数
            successNum++;
            // 行数加1
            row++;
        }

        //计数
        excelResultDTO.setSum(excelList.size());
        excelResultDTO.setSuccess(successNum);
        List<ExcelResultDetailDTO> fail = excelResultDetailDTOS.stream()
                .filter(e -> Integer.valueOf(1).equals(e.getType())).collect(Collectors.toList());
        excelResultDTO.setFail(fail.size());
        excelResultDTO.setExcelResultDetailDTOS(excelResultDetailDTOS);
        return excelResultDTO;
    }

    /**
    * @Description: 批量解绑
    * @Param: [deviceCodeList]
    * @return: site.morn.rest.RestMessage
    * @Author: 胡明威
    * @Date: 2023/9/21
    */
    @Override
    public RestMessage untie(Set<Integer> ids) {
        LinkappUser curUserInfo = linkappUserContextProducer.getNotNullCurrent();
        String tenantId = curUserInfo.getTenantId();
        LambdaUpdateWrapper<LinkappLocationDevice> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(LinkappLocationDevice::getBindTime, null);
        updateWrapper.set(LinkappLocationDevice::getUserId, null);
        updateWrapper.set(LinkappLocationDevice::getBindState, 0);
        updateWrapper.set(LinkappLocationDevice::getModifyId, curUserInfo.getId());
        updateWrapper.set(LinkappLocationDevice::getModifyTime, new Date());
        updateWrapper.in(LinkappLocationDevice::getId, ids);
        update(updateWrapper);
        return RestBuilders.successBuilder().message("批量解绑成功").build();
    }

    /**
    * @Description: 从设备列表同步安全帽数据到安全帽管理
    * @Param: []
    * @return: site.morn.rest.RestMessage
    * @Author: 胡明威
    * @Date: 2023/9/22
    */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public RestMessage synchronizeLocationDevice() {
        LinkappUser curUserInfo = linkappUserContextProducer.getNotNullCurrent();
        List<String> deviceCodeList = new ArrayList<>();

        // 查询当前项目设备信息
        DeviceTypeDto dto = new DeviceTypeDto();
        dto.setDeviceTypeName("室外人员定位器");
        dto.setTenantId(curUserInfo.getTenantId());
        List<Device> deviceList = linkappLocationDeviceMapper.queryProjectDevice(dto);
        // 如果设备列表为空，则清空定位器设备数据
        if (CollectionUtil.isEmpty(deviceList)) {
            QueryWrapper<LinkappLocationDevice> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("tenant_id_", curUserInfo.getTenantId());
            remove(queryWrapper);
            return RestBuilders.successBuilder().message("同步成功").build();
        }
        deviceList.forEach(device -> {
            QueryWrapper<LinkappLocationDevice> wrapper = new QueryWrapper<>();
            wrapper.eq("device_code_", device.getCode());
            wrapper.eq("tenant_id_", curUserInfo.getTenantId());
            LinkappLocationDevice locationDevice = getOne(wrapper);
            if (locationDevice == null) {
                // 新增定位设备
                LinkappLocationDevice createdDevice = createDevice(device, curUserInfo);
                save(createdDevice);
            } else {
                // 更新定位设备
                Integer deviceId = locationDevice.getId();
                LinkappLocationDevice modifiedDevice = modifyDevice(device, curUserInfo, deviceId);
                updateById(modifiedDevice);
            }
            deviceCodeList.add(device.getCode());
        });
        // 查询所有定位设备
        QueryWrapper<LinkappLocationDevice> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("tenant_id_", curUserInfo.getTenantId());
        queryWrapper.eq("device_type_", 1);
        List<LinkappLocationDevice> list = list(queryWrapper);
        // 获取定位设备编码列表
        List<String> locationDeviceCodeList = list.stream().map(item -> item.getDeviceCode()).collect(Collectors.toList());
        // 找出定位设备列表中不存在于设备列表的设备编码，并移除这些定位设备
        Collection notExists = new ArrayList<>(locationDeviceCodeList);
        notExists.removeAll(deviceCodeList);
        if (CollectionUtil.isNotEmpty(notExists)) {
            QueryWrapper<LinkappLocationDevice> qw = new QueryWrapper<>();
            qw.in( "device_code_", notExists);
            qw.eq("tenant_id_", curUserInfo.getTenantId());
            remove(qw);
        }
        return RestBuilders.successBuilder().message("同步成功").build();
    }

    /**
    * @Description: 定位器流水处理
    * @Param: [datapushDTO]
    * @return: void
    * @Author: 胡明威
    * @Date: 2023/9/25
    */
    @Override
    public void datapushHandler(DatapushDTO datapushDTO) throws JsonProcessingException {
        String deviceCode = datapushDTO.getDevice_id();
        // 根据定位设备编码查找设备
        QueryWrapper<LinkappLocationDevice> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("device_code_", deviceCode).orderByDesc("modify_time_");
        List<LinkappLocationDevice> list = list(queryWrapper);
        // 如果查询不到定位设备，则直接返回
        if (CollectionUtil.isEmpty(list)) {
            return;
        }
        LinkappLocationDevice locationDevice = list.get(0);
        String tenantId = locationDevice.getTenantId();
        String userId = locationDevice.getUserId();
        JSONObject data = datapushDTO.getData();
        LinkappLocationDevice device = new LinkappLocationDevice();
        if ("2".equals(data.getString("message_type"))) {
            device.setBattery(data.getInteger("battery_level"));
            device.setLatestReportingTime(new Date());
            device.setOnlinestate(true);

            UpdateWrapper<LinkappLocationDevice> updateWrapper = new UpdateWrapper<>();
            updateWrapper.eq("device_code_", deviceCode);
            updateWrapper.eq("tenant_id_", tenantId);
            update(device, updateWrapper);
            return;
        } else if ("8".equals(data.getString("message_type"))) {
            device.setWarningState(true);
            device.setLatestReportingTime(new Date());
            device.setOnlinestate(true);

            UpdateWrapper<LinkappLocationDevice> updateWrapper = new UpdateWrapper<>();
            updateWrapper.eq("device_code_", deviceCode);
            updateWrapper.eq("tenant_id_", tenantId);
            update(device, updateWrapper);
            return;
        }
        int beaconNum = data.getIntValue("positioning_beacon_number");

        // 更新定位器数据
        device.setBattery(data.getInteger("battery_level"));
        device.setWearingState(Integer.valueOf(1).equals(data.getInteger("wearing_state")));
        device.setLocationType("3".equals(data.getString("message_type")));
        LambdaUpdateWrapper<LinkappLocationDevice> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(LinkappLocationDevice::getWearingState, Integer.valueOf(1).equals(data.getInteger("wearing_state")));
        updateWrapper.set(LinkappLocationDevice::getLocationType, "3".equals(data.getString("message_type")));
        // todo 信标编码待确定
        JSONArray dataArray = data.getJSONArray("rssis");
        JSONArray beaconArray = data.getJSONArray("majorMinors");
        // todo 高度计算待调整
//        Float deviceHeight = data.getFloat("height");

        String beaconDeviceCode = null;
        String indoorLocation = null;
        List<Integer> rssisList = new ArrayList<>();
        List<String> beaconList = new ArrayList<>();
        double[] devicePosition = new double[2];
        boolean flag = false;
        // 信标过滤
        List<Integer> newRssisList = new ArrayList<>();
        List<String> newBeaconList = new ArrayList<>();
        if (dataArray != null && beaconArray != null) {
            rssisList = new ObjectMapper().readValue(dataArray.toString(), new TypeReference<List<Integer>>() {});
            beaconList = new ObjectMapper().readValue(beaconArray.toString(), new TypeReference<List<String>>() {});
            for (int i = 0; i < beaconList.size(); i++) {
                AppFloorBeaconVO appFloorBeacon = new AppFloorBeaconVO();
                appFloorBeacon.setDeviceCode(beaconList.get(i));
                appFloorBeacon.setTenantId(tenantId);
                List<AppFloorBeaconVO> beaconVOS = floorBeaconMapper.getBeaconList(new Page<>(-1, -1), appFloorBeacon).getRecords();
                if (CollectionUtil.isNotEmpty(beaconVOS)) {
                    newRssisList.add(rssisList.get(i));
                    newBeaconList.add(beaconList.get(i));
                }
            }
            if (CollectionUtil.isNotEmpty(newRssisList) && CollectionUtil.isNotEmpty(newBeaconList)) {
                Integer max = Collections.max(newRssisList);
                beaconDeviceCode = newBeaconList.get(newRssisList.indexOf(max));
                AppFloorBeaconVO appFloorBeacon = new AppFloorBeaconVO();
                appFloorBeacon.setDeviceCode(beaconDeviceCode);
                appFloorBeacon.setTenantId(tenantId);
                List<AppFloorBeaconVO> beaconVOS = floorBeaconMapper.getBeaconList(new Page<>(-1, -1), appFloorBeacon).getRecords();
                indoorLocation =  CollectionUtil.isEmpty(beaconVOS) ? null : beaconVOS.get(0).getLocation();
            }

            // 计算定位器在平面图上的坐标
            devicePosition = calculateDevicePosition(newRssisList, newBeaconList, tenantId);
            flag = true;
        }
//        List<String> compareList = new ArrayList<>();
        // 最高rssi值
//        Integer max = Collections.max(rssisList);
//        for (int i = 0; i < beaconNum; i++) {
//            if (max - rssisList.get(i) <= 5) {
//                compareList.add(beaconList.get(i));
//            }
//        }


        // 定位原理
//        if (compareList.size() == 1) {
//            beaconDeviceCode = compareList.get(0);
//            AppFloorBeaconVO appFloorBeaconVO = new AppFloorBeaconVO();
//            appFloorBeaconVO.setDeviceCode(beaconDeviceCode);
//            appFloorBeaconVO.setTenantId(tenantId);
//            List<AppFloorBeaconVO> records = floorBeaconMapper.getBeaconList(null, appFloorBeaconVO).getRecords();
//            indoorLocation = records == null ? null : records.get(0).getLocation();
//        } else {
//            for (String code : compareList) {
//                AppFloorBeaconVO appFloorBeaconVO = new AppFloorBeaconVO();
//                appFloorBeaconVO.setDeviceCode(code);
//                appFloorBeaconVO.setTenantId(tenantId);
//                List<AppFloorBeaconVO> records = floorBeaconMapper.getBeaconList(null, appFloorBeaconVO).getRecords();
//                if (CollectionUtil.isNotEmpty(records)) {
//                    Float minHeight = records.get(0).getMinHeight();
//                    Float maxHeight = records.get(0).getMaxHeight();
//                    if (deviceHeight <= maxHeight && deviceHeight >= minHeight) {
//                        beaconDeviceCode = code;
//                        indoorLocation = records.get(0).getLocation();
//                        break;
//                    }
//                }
//            }
//        }
        // 计算信标对应高度
        AppFloorBeaconVO appFloorBeacon = new AppFloorBeaconVO();
        appFloorBeacon.setDeviceCode(beaconDeviceCode);
        appFloorBeacon.setTenantId(tenantId);
        List<AppFloorBeaconVO> beaconRecords = floorBeaconMapper.getBeaconList(new Page<>(-1, -1), appFloorBeacon).getRecords();
        Float height = StringUtils.isBlank(beaconDeviceCode) || CollectionUtil.isEmpty(beaconRecords) ? new Integer(0).floatValue() : beaconRecords.get(0).getMinHeight();
        Float longitude = data.getFloat("longitude") == null ? new Integer(0).floatValue() : data.getFloat("longitude");
        Float latitude = data.getFloat("latitude") == null ? new Integer(0).floatValue() : data.getFloat("latitude");
        // 最优信标编号
        device.setLabelCode(beaconDeviceCode);
        device.setIndoorLocation(indoorLocation);
        device.setLongitude(longitude);
        device.setLatitude(latitude);
        device.setHeight(height);
        device.setLatestReportingTime(new Date());
        device.setOnlinestate(true);
        updateWrapper.set(LinkappLocationDevice::getLabelCode, beaconDeviceCode);
        updateWrapper.set(LinkappLocationDevice::getIndoorLocation, indoorLocation);
        updateWrapper.set(LinkappLocationDevice::getLongitude, longitude);
        updateWrapper.set(LinkappLocationDevice::getLatitude, latitude);
        updateWrapper.set(LinkappLocationDevice::getHeight, height);
        updateWrapper.set(LinkappLocationDevice::getLatestReportingTime, new Date());
        updateWrapper.set(LinkappLocationDevice::getOnlinestate, true);
        updateWrapper.set(LinkappLocationDevice::getXPosition, flag && !Double.isNaN(devicePosition[0]) ? devicePosition[0] : null);
        updateWrapper.set(LinkappLocationDevice::getYPosition, flag && !Double.isNaN(devicePosition[1]) ? devicePosition[1] : null);
        updateWrapper.eq(LinkappLocationDevice::getDeviceCode, deviceCode);
        updateWrapper.eq(LinkappLocationDevice::getTenantId, tenantId);
        update(updateWrapper);

        //人员区域操作
        updateArea(device, tenantId, userId);

        // 记录定位历史
        LinkappLocationRecord linkappLocationRecord = createRecordObject(device, deviceCode, tenantId, userId);
        linkappLocationRecordService.save(linkappLocationRecord);

        // 保存定位历史明细
        for (int i = 0; i < beaconNum; i++) {
            AppFloorBeaconVO appFloorBeaconVO = new AppFloorBeaconVO();
            appFloorBeaconVO.setDeviceCode(newBeaconList.get(i));
            appFloorBeaconVO.setTenantId(tenantId);
            List<AppFloorBeaconVO> records = floorBeaconMapper.getBeaconList(new Page<>(-1, -1), appFloorBeaconVO).getRecords();
            indoorLocation = CollectionUtil.isEmpty(records) ? null : records.get(0).getLocation();
            height = CollectionUtil.isEmpty(records) ? null : records.get(0).getMinHeight();

            LinkappLocationRecordDetail locationRecordDetail = new LinkappLocationRecordDetail();
            locationRecordDetail.setLinkId(Long.valueOf(linkappLocationRecord.getId()));
            locationRecordDetail.setDeviceCode(newBeaconList.get(i));
            locationRecordDetail.setTenantId(tenantId);
            locationRecordDetail.setIndoorLocation(indoorLocation);
            locationRecordDetail.setHeight(height);
            locationRecordDetail.setSignalStrength(String.valueOf(newRssisList.get(i)));
            locationRecordDetail.setDeleteState(true);
            locationRecordDetail.setCreator("SYS");
            locationRecordDetail.setCreateTime(new Date());
            locationRecordDetail.setModifier("SYS");
            locationRecordDetail.setModifyTime(new Date());
            linkappLocationRecordDetailService.save(locationRecordDetail);
        }
    }

    /**
    * @Description: 人员查询
    * @Param: [dto]
    * @return: site.morn.rest.RestMessage
    * @Author: 胡明威
    * @Date: 2023/9/27
    */
    @Override
    public RestMessage queryPeopleInfo(PeopleQueryDto dto) {
        // 获取登录人信息
        LinkappUser curUserInfo = linkappUserContextProducer.getNotNullCurrent();
        dto.setTenantId(curUserInfo.getTenantId());
        PeopleInfoVo peopleInfoVo = baseMapper.queryPeopleInfo(dto);
        // 计算年龄
        peopleInfoVo.setAge(DateUtil.age(peopleInfoVo.getBirthday(), new Date()));
        // 计算证书是否超期
        List<UserCertificateDTO> userCertificateDTOS = userCertificateService.queryList(new UserCertificate().setUserId(dto.getUserId()));
        for (UserCertificateDTO userCertificateDTO : userCertificateDTOS) {
            if (!Integer.valueOf(1).equals(userCertificateDTO.getStatus())) {
                peopleInfoVo.setCertificateTime(2);
                return RestBuilders.successBuilder().data(peopleInfoVo).build();
            }
        }
        peopleInfoVo.setCertificateTime(1);
        return RestBuilders.successBuilder().data(peopleInfoVo).build();
    }

    /**
    * @Description: 获取当前项目打点数据
    * @Param: []
    * @return: site.morn.rest.RestMessage
    * @Author: 胡明威
    * @Date: 2023/9/27
    */
    @Override
    public RestMessage getLocationPoints() {
        // 获取租户id
        String tenantId = linkappUserContextProducer.getNotNullCurrent().getTenantId();
        // 获取室内位置打点信息
        List<IndoorPointsVo> indoorPoints = linkappLocationDeviceMapper.queryIndoorPoints(tenantId);
        // 获取室外位置打点信息
        List<OutdoorPointsVo> outdoorPoints = linkappLocationDeviceMapper.queryOutdoorPoints(tenantId);
        PointsVo points = new PointsVo();
        points.setIndoorPointsVoList(indoorPoints);
        points.setOutdoorPointsVoList(outdoorPoints);
        return RestBuilders.successBuilder(points).build();
    }

    @Override
    public RestMessage getIndoorLocationInfo(IndoorLocationDto dto) {
        dto.setTenantId(linkappUserContextProducer.getNotNullCurrent().getTenantId());
        // 获取人员室内定位信息
        List<IndoorLocationVo> indoorLocationVos = linkappLocationDeviceMapper.queryIndoorLocationInfo(dto);
        IndoorLocationInfoVo locationInfoVo = new IndoorLocationInfoVo();
        locationInfoVo.setIndoorLocationVoList(indoorLocationVos);
        // 获取楼层信息
        AppFloorBeaconVO appFloorBeaconVO = new AppFloorBeaconVO();
        appFloorBeaconVO.setFloorId(dto.getFloorId());
        AppFloorBeaconVO floorInfo = linkappLocationDeviceMapper.queryFloorInfo(appFloorBeaconVO);
        if (floorInfo == null) {
            throw new BusinessException("查询不到楼层信息");
        }
        locationInfoVo.setIndoorLocation(floorInfo.getLocation());
        locationInfoVo.setPicUrl(floorInfo.getFileUrl());
        return RestBuilders.successBuilder(locationInfoVo).build();
    }

    /**
     * 获取大屏人数统计信息
     * @return
     */
    @Override
    public RestMessage countNumberOfDevicePeople() {
        LinkappUser currentUser = linkappUserContextProducer.getNotNullCurrent();
        AppFloorBeaconVO appFloorBeaconVO = new AppFloorBeaconVO();
        appFloorBeaconVO.setTenantId(currentUser.getTenantId());
        DeviceQueryDto deviceQueryDto = new DeviceQueryDto();
        deviceQueryDto.setTenantId(currentUser.getTenantId());
        deviceQueryDto.setBindState(1);
        List<LocationDeviceVo> locationDeviceVos = linkappLocationDeviceMapper.queryList(deviceQueryDto);
        List<AppFloorBeaconVO> beaconList = floorBeaconMapper.getBeaconList(appFloorBeaconVO);
        ScreenCountVo screenCountVo = new ScreenCountVo();
        screenCountVo.setPeopleNumber(locationDeviceVos.size());
        screenCountVo.setBeaconNumber(beaconList.size());
        //统计区域人数
        List<AreaObject> areas = new ArrayList<>();
        List<BuildingFloorVO> buildingFloorVOS = buildingService.getTreeWithNum();
        long count1 = 0;
        long count2 = 0;
        long count3 = 0;

        // 获取租户id
        String tenantId = linkappUserContextProducer.getNotNullCurrent().getTenantId();
        // 获取室外位置打点信息
        List<OutdoorPointsVo> outdoorPoints = linkappLocationDeviceMapper.queryOutdoorPoints(tenantId);
        //查询项目下不同区域的区域坐标
        List<LaborRegionDTO> laborRegionDTOS = laborRegionService.queryListByTenantId(tenantId);
        for (OutdoorPointsVo outdoorPointsVo : outdoorPoints) {
            int area = whichArea(Float.parseFloat(outdoorPointsVo.getLongitude()), Float.parseFloat(outdoorPointsVo.getLatitude()), laborRegionDTOS);
            if (area == 1) count1++;
            if (area == 2) count2++;
            if (area == 3) count3++;
        }

        for (BuildingFloorVO buildingFloorVO :
                buildingFloorVOS) {
            if (buildingFloorVO.getBuildingName().equals("会议室")) {
                count2 += buildingFloorVO.getNum();
            }
            else if (buildingFloorVO.getBuildingName().equals("生活区")) {
                count3 += buildingFloorVO.getNum();
            }
            else {
                count1 += buildingFloorVO.getNum();
            }
        }
        AreaObject areaObject1 = new AreaObject("施工区", count1);
        AreaObject areaObject2 = new AreaObject("办公区", count2);
        AreaObject areaObject3 = new AreaObject("生活区", count3);
        areas.add(areaObject1);
        areas.add(areaObject2);
        areas.add(areaObject3);
        screenCountVo.setAreaList(areas);

//        List<AreaObject> areas = new ArrayList<>();
//        long count1 = locationDeviceVos.stream().filter(u -> Integer.valueOf(1).equals(u.getPeopleArea())).count();
//        AreaObject areaObject1 = new AreaObject("施工区", count1);
//        areas.add(areaObject1);
//        long count2 = locationDeviceVos.stream().filter(u -> Integer.valueOf(2).equals(u.getPeopleArea())).count();
//        AreaObject areaObject2 = new AreaObject("办公区", count2);
//        areas.add(areaObject2);
//        long count3 = locationDeviceVos.stream().filter(u -> Integer.valueOf(3).equals(u.getPeopleArea())).count();
//        AreaObject areaObject3 = new AreaObject("生活区", count3);
//        areas.add(areaObject3);
//        screenCountVo.setAreaList(areas);
        //统计工种人数
        Map<String, Long> map = locationDeviceVos.stream()
                .filter(k -> k.getWorkTypeName() != null)
                .collect(Collectors.groupingBy(LocationDeviceVo::getWorkTypeName, Collectors.counting()));
        List<WorkTypeObject> workTypeObjects = new ArrayList<>();
        map.forEach((k, v) -> {
            WorkTypeObject workTypeObject = new WorkTypeObject(k, v);
            workTypeObjects.add(workTypeObject);
        });
        //按count降序排列
        workTypeObjects.stream().sorted(Comparator.comparing(WorkTypeObject::getWorkTypeNumber).reversed()).collect(Collectors.toList());
        screenCountVo.setWorkTypeList(workTypeObjects);
        return RestBuilders.successBuilder().data(screenCountVo).build();
    }


    @Scheduled(cron = "0 0 3 * * ?")
    public void bindPeople() {
        try {
            logger.info("定位设备人员绑定定时任务开始==================================================");
            // 查询待绑定设备信息
            QueryWrapper<LinkappLocationDevice> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("bind_state_", 2).eq("delete_state_", 1);
            List<LinkappLocationDevice> list = list(queryWrapper);

            // 查询待绑定人员是否已录入花名册
            for (LinkappLocationDevice locationDevice : list) {
                UserProjectDTO userInfo = linkappLocationDeviceMapper.queryUserProjectInfo(locationDevice.getTenantId(), locationDevice.getUserId());
                if (userInfo == null) {
                    logger.info("设备: " + locationDevice.getDeviceCode() + " 绑定失败, 原因: 身份证号(" + locationDevice.getUserId() + ")对应人员未录入花名册");
                    continue;
                }
                // 绑定人员信息
                locationDevice.setBindState(1);
                locationDevice.setUserId(userInfo.getId());
                locationDevice.setBindTime(new Date());
                boolean flag = updateById(locationDevice);
                if (flag) {
                    logger.info("设备: " + locationDevice.getDeviceCode() + " 与 " + userInfo.getUserName() + " 绑定成功");
                } else {
                    logger.info("设备: " + locationDevice.getDeviceCode() + " 与 " + userInfo.getUserName() + " 绑定失败");
                }
            }
            logger.info("定位设备人员绑定定时任务结束==================================================");
        } catch (Exception e) {
            logger.error("定位设备人员绑定定时任务异常==================================================", e);
        }
    }


    /**
    * @Description: 校验设备编码
    * @Param: [deviceCode]
    * @return: boolean
    * @Author: 胡明威
    * @Date: 2023/9/21
    */
    private boolean checkDeviceCode(String deviceCode, List<String> msg, String tenantId) {
        if (StringUtils.isBlank(deviceCode)) {
            msg.add("设备编码为空");
            return false;
        }
        QueryWrapper<LinkappLocationDevice> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("device_code_", deviceCode);
        queryWrapper.eq("delete_state_", 1);
        queryWrapper.eq("tenant_id_", tenantId);
        LinkappLocationDevice one = getOne(queryWrapper);
        if (one == null) {
            msg.add("设备不存在");
            return false;
        }
        return true;
    }

    /**
    * @Description: 校验姓名
    * @Param: [name]
    * @return: boolean
    * @Author: 胡明威
    * @Date: 2023/9/21
    */
    private boolean checkName(String name, List<String> msg, String tenantId) {
        if (StringUtils.isBlank(name)) {
            msg.add("人员为空");
            return false;
        }
        QueryWrapper<UserProject> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_name_", name);
        queryWrapper.eq("tenant_id_", tenantId);
        List<UserProject> list = userProjectService.list(queryWrapper);
        if (CollectionUtil.isEmpty(list)) {
            msg.add("人员不存在");
            return false;
        }
        return true;
    }

    /**
    * @Description: 校验身份证号
    * @Param: [cardNo]
    * @return: userId
    * @Author: 胡明威
    * @Date: 2023/9/21
    */
    private String checkCardNo(String cardNo, List<String> msg, String tenantId, String userName,
                               Set<String> cardSet) {
        if (StringUtils.isBlank(cardNo)) {
            msg.add("身份证为空");
            return null;
        } else if (!IdentityUtils.isLegalPattern(cardNo)) {
            msg.add("身份证格式错误");
            return null;
        } else if (cardSet.contains(cardNo)) {
            msg.add("与表格中身份证数据重复");
            return null;
        } else {
            cardSet.add(cardNo);
        }
        UserProjectDTO userProjectDTO = new UserProjectDTO();
        userProjectDTO.setUserName(cardNo);
        userProjectDTO.setTenantId(tenantId);
        IPage<UserProjectDTO> result = userProjectMapper.queryListByPage(new Page<>(-1, -1), userProjectDTO);
        if (CollectionUtil.isEmpty(result.getRecords())) {
            msg.add("身份证号错误");
            return null;
        } else if (!userName.equals(result.getRecords().get(0).getUserName())){
            msg.add("姓名与身份证号不匹配");
            return null;
        }
        return result.getRecords().get(0).getId();
    }

    /**
    * @Description: 生成新增定位设备对象
    * @Param: [device, curUserInfo]
    * @return: com.easylinkin.linkappapi.location.entity.LinkappLocationDevice
    * @Author: 胡明威
    * @Date: 2023/9/22
    */
    private LinkappLocationDevice createDevice(Device device, LinkappUser curUserInfo) {
        LinkappLocationDevice newDevice = new LinkappLocationDevice();
        newDevice.setDeviceCode(device.getCode());
        newDevice.setDeviceType(1);
        newDevice.setTenantId(curUserInfo.getTenantId());
        newDevice.setBattery(device.getBattery() == null ? null : Integer.valueOf(device.getBattery().toString()));
        newDevice.setOnlinestate(Integer.valueOf(1).equals(device.getOnlineState()) ? true : false);
        newDevice.setDeleteState(true);
        newDevice.setLatestReportingTime(device.getLastPushTime());
        newDevice.setLongitude(StringUtils.isBlank(device.getLongitude()) ? null : Float.valueOf(device.getLongitude()));
        newDevice.setLatitude(StringUtils.isBlank(device.getLatitude()) ? null : Float.valueOf(device.getLatitude()));
        newDevice.setCreatorId(curUserInfo.getId());
        newDevice.setCreateTime(new Date());
        newDevice.setModifyId(curUserInfo.getId());
        newDevice.setModifyTime(new Date());
        return newDevice;
    }

    /**
    * @Description: 生成修改定位设备对象
    * @Param: [device, curUserInfo, deviceId]
    * @return: com.easylinkin.linkappapi.location.entity.LinkappLocationDevice
    * @Author: 胡明威
    * @Date: 2023/9/22
    */
    private LinkappLocationDevice modifyDevice(Device device, LinkappUser curUserInfo, Integer deviceId) {
        LinkappLocationDevice newDevice = new LinkappLocationDevice();
        newDevice.setId(deviceId);
        newDevice.setBattery(device.getBattery() == null ? null : Integer.valueOf(device.getBattery().toString()));
        newDevice.setOnlinestate(Integer.valueOf(1).equals(device.getOnlineState()) ? true : false);
        newDevice.setLatestReportingTime(device.getLastPushTime());
        newDevice.setLongitude(StringUtils.isBlank(device.getLongitude()) ? null : Float.valueOf(device.getLongitude()));
        newDevice.setLatitude(StringUtils.isBlank(device.getLatitude()) ? null : Float.valueOf(device.getLatitude()));
        newDevice.setModifyId(curUserInfo.getId());
        newDevice.setModifyTime(new Date());
        return newDevice;
    }

    private LinkappLocationRecord createRecordObject(LinkappLocationDevice device, String deviceCode, String tenantId, String userId) {
        LinkappLocationRecord linkappLocationRecord = new LinkappLocationRecord();
        linkappLocationRecord.setDeviceCode(deviceCode);
        linkappLocationRecord.setDeviceType(1);
        linkappLocationRecord.setTenantId(tenantId);
        linkappLocationRecord.setUserInfo(userId);
        linkappLocationRecord.setLocationType(device.getLocationType());
        linkappLocationRecord.setIndoorLocation(device.getIndoorLocation());
        linkappLocationRecord.setHeight(device.getHeight());
        linkappLocationRecord.setLongitude(device.getLongitude());
        linkappLocationRecord.setLatitude(device.getLatitude());
        linkappLocationRecord.setBestBeacon(device.getLabelCode());
        linkappLocationRecord.setDeleteState(true);
        linkappLocationRecord.setCreator("SYS");
        linkappLocationRecord.setCreateTime(new Date());
        linkappLocationRecord.setModifier("SYS");
        linkappLocationRecord.setModifyTime(new Date());
        return linkappLocationRecord;
    }

    private double[] calculateDevicePosition(List<Integer> rssisList, List<String> beaconList, String tenantId) {
        double[] accessXPositions = new double[rssisList.size()];
        double[] accessYPositions = new double[rssisList.size()];
        double[] rssiValues = new double[rssisList.size()];
        int ind = 0;

        // 获取平面图信标的x轴、y轴坐标
        for (String beaconCode : beaconList) {
            AppFloorBeaconVO appFloorBeacon = new AppFloorBeaconVO();
            appFloorBeacon.setDeviceCode(beaconCode);
            appFloorBeacon.setTenantId(tenantId);
            List<AppFloorBeaconVO> beaconVOS = floorBeaconMapper.getBeaconList(new Page<>(-1, -1), appFloorBeacon).getRecords();
            if (CollectionUtil.isEmpty(beaconVOS)) {
                continue;
            }
            Float x =  CollectionUtil.isEmpty(beaconVOS) ? null : beaconVOS.get(0).getBeaconXPosition();
            Float y =  CollectionUtil.isEmpty(beaconVOS) ? null : beaconVOS.get(0).getBeaconYPosition();
            accessXPositions[ind] = x;
            accessYPositions[ind] = y;
            ind++;
        }

        for (int i=0; i<rssisList.size(); i++) {
            rssiValues[i] = rssisList.get(i);
        }

        // 三点定位，预测坐标
        double estimatedX = ThreePointLocationUtil.estimatePosition(accessXPositions, rssiValues);
        double estimatedY = ThreePointLocationUtil.estimatePosition(accessYPositions, rssiValues);
        double[] result = {estimatedX, estimatedY};
        return result;
    }

    /**
     * 更新人员区域
     * @param device
     */
    private void updateArea(LinkappLocationDevice device, String tenantId, String userId) {
        //查询该项目属于哪种考勤在场判断
        LobarSet lobarSet = lobarSetService.getByTenantId(tenantId);
        if (1 != lobarSet.getOnType()) {
            //如果不是人员定位则直接返回
            return;
        }
        //查询项目下不同区域的区域坐标
        List<LaborRegionDTO> laborRegionDTOS = laborRegionService.queryListByTenantId(tenantId);
        if (device.getLocationType()) {
            //室外定位
            //更新人员区域
            Integer area = whichArea(device.getLongitude(), device.getLatitude(), laborRegionDTOS);
            updateUserProjectById(userId, area);
        } else {
            //室内定位
            AppFloorBeaconVO appFloorBeaconVO = new AppFloorBeaconVO();
            appFloorBeaconVO.setTenantId(tenantId);
            appFloorBeaconVO.setDeviceCode(device.getLabelCode());
            List<AppFloorBeaconVO> beaconVOS = floorBeaconMapper.getBeaconListByCode(appFloorBeaconVO);
            if (beaconVOS.size() > 0) {
                Integer area = whichArea(beaconVOS.get(0).getLongitude(), beaconVOS.get(0).getLatitude(), laborRegionDTOS);
                updateUserProjectById(userId, area);
            }
        }
    }

    /**
     * 根据userprojectid更新人员区域
     * @param userId
     * @param area
     */
    private void updateUserProjectById(String userId, Integer area) {
        UserProject userProject = new UserProject();
        userProject.setId(userId);
        userProject.setOnArea(area);
        userProjectMapper.updateById(userProject);
    }

    /**
     * 计算经纬度对应的区域范围
     * @param longitude
     * @param latitude
     * @param laborRegionDTOS
     * @return
     */
    private Integer whichArea(Float longitude, Float latitude, List<LaborRegionDTO> laborRegionDTOS) {
        Integer area = 4;//默认项目外
        for (LaborRegionDTO laborRegionDTO : laborRegionDTOS) {
            boolean x = GaodeUtils.isPtInPoly(longitude, latitude, laborRegionDTO.getRegionDTOList());
            if (x) {
                area = laborRegionDTO.getType();
            }
        }
        return area;
    }


}
